﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.ComponentModel;
using System.Reflection;
using System.Web;
using System.Web.Compilation;
using System.IO;
using System.Collections.Specialized;
using System.Linq.Expressions;
using System.Data;

namespace System.Web.Mvc {
    internal class MvcControlDataBinder {
        #region DataBinding
        internal static DataTable EvalDataSource(object dataSource, string textField, string valueField) {
            DataTable tbl = null;

            if (String.IsNullOrEmpty(textField) && String.IsNullOrEmpty(valueField)) {

                //no columns were passed in - use the entire datasource
                tbl = SourceToTable(dataSource);

                //validate
                ValidateDataSource(tbl);


            } else {
                if (!String.IsNullOrEmpty(textField) && String.IsNullOrEmpty(valueField)) {
                    tbl = SourceToTable(dataSource, textField, textField);
                } else {
                    tbl = SourceToTable(dataSource, valueField, textField);

                }
                //validate
                ValidateDataSource(tbl);
            }
            return tbl;
        }
        internal static void ValidateDataSource(DataTable tbl) {
            if (tbl == null)
                throw new Exception("Invalid data source. There must be at least one column/property present");

            if (tbl.Columns.Count < 1)
                throw new Exception("Invalid data source. Must be IEnumerable, IQueryable, a DataSet, a DataTable, or an IDataReader");

        }
        internal static DataTable CopyTable(DataTable tblFrom, object[] keeperColumns) {
            DataTable result = new DataTable();

            if (keeperColumns != null) {
                //can't just remove the columns, since that will throw an enumeration error
                //have to copy over the structure "manually"
                foreach (DataColumn dc in tblFrom.Columns) {
                    string thisColumnName = dc.ColumnName.ToLower().Trim();
                    foreach (object o in keeperColumns) {
                        if (o.ToString().ToLower().Equals(thisColumnName)) {
                            result.Columns.Add(thisColumnName);
                            break;
                        }
                    }
                }

            } else {
                result = tblFrom.Clone();
            }

            //add the data
            foreach (DataRow dr in tblFrom.Rows) {
                DataRow newRow = result.NewRow();
                foreach (DataColumn resultCol in result.Columns) {
                    newRow[resultCol.ColumnName] = dr[resultCol.ColumnName];
                }
                result.Rows.Add(newRow);
            }


            return result;
        }

        internal static DataTable SourceToTable(object dataSource) {
            return SourceToTable(dataSource, null);
        }
        //This method is ridiculously long.
        //TODO: Refactor ME! Will update to use Eilon's bits
        internal static DataTable SourceToTable(object dataSource, params object[] dataSourceKeys) {

            DataTable result = new DataTable(); 
            IEnumerator en = null;
            if (dataSource is IDataReader) {
                //Loop the reader and put the results in a table
                //so we can iterate over it.
                IDataReader rdrSource = dataSource as IDataReader;
                if (dataSourceKeys != null) {
                    foreach (object o in dataSourceKeys) {
                        DataColumn dc = new DataColumn();
                        dc.ColumnName = o.ToString();
                        result.Columns.Add(dc);

                    }
                }

                while (rdrSource.Read()) {
                    DataRow dr = result.NewRow();
                    string rdrVal = "";
                    if (dataSourceKeys != null) {
                        foreach (object o in dataSourceKeys) {
                            rdrVal = rdrSource[o.ToString()].ToString();
                            dr[o.ToString()] = rdrVal;
                        }
                    } else {

                        //build the table if it hasn't been set yet
                        if (result.Columns.Count == 0) {
                            for (int i = 0; i < rdrSource.FieldCount; i++) {
                                DataColumn dc = new DataColumn(rdrSource.GetName(i)); ;
                                result.Columns.Add(dc);
                            }
                        }

                        foreach (DataColumn dc in result.Columns) {
                            rdrVal = rdrSource[dc.ColumnName].ToString();
                            dr[dc.ColumnName] = rdrVal;
                        }
                    }
                    result.Rows.Add(dr);
                }
                rdrSource.Close();

            } else if (dataSource is DataSet || dataSource is DataTable) {
                //temporary data holder
                DataTable stubTable = null;
                if (dataSource is DataSet) {
                    DataSet ds = dataSource as DataSet;

                    if (ds.Tables.Count > 0)
                        stubTable = ds.Tables[0];
                } else {
                    stubTable = dataSource as DataTable;
                }

                result = CopyTable(stubTable, dataSourceKeys);
            
            } else if (dataSource is Array) {
                result.Columns.Add("Value");
                result.Columns.Add("Text");
                IEnumerable<object> arraySource = dataSource as IEnumerable<object>;
                en = arraySource.GetEnumerator();

                while (en.MoveNext()) {
                    DataRow dr = result.NewRow();
                    dr[0] = en.Current.ToString();
                    dr[1] = dr[0];
                    result.Rows.Add(dr);
                }
                

            } else {
                if (dataSource is IQueryable) {
                    IQueryable qSource = dataSource as IQueryable;

                    //enumerate it
                    en = qSource.GetEnumerator();

                } else if (dataSource is IEnumerable) {
                    IEnumerable enumSource = dataSource as IEnumerable;
                    en = enumSource.GetEnumerator();


                }

                if (en != null) {


                    //loop the enumerator, and pull out all properties for the current object
                    //put that into the result table
                    PropertyInfo[] props = null;
                    int indexer = 0;
                    while (en.MoveNext()) {

                        //set the prop array and result table on first pass
                        if (indexer == 0) {
                            //set the prop array
                            props = en.Current.GetType().GetProperties();

                            //add columns to the table
                            if (dataSourceKeys != null) {
                                foreach (object o in dataSourceKeys)
                                    result.Columns.Add(new DataColumn(o.ToString()));
                            } else {
                                foreach (PropertyInfo p in props)
                                    result.Columns.Add(new DataColumn(p.Name));
                            }
                        }

                        //if there are no columns in the table, build it
                        //loop the properties, setting each row accordingly
                        DataRow dr = result.NewRow();
                        PropertyInfo pKeyed = null;
                        if (dataSourceKeys != null) {
                            foreach (object o in dataSourceKeys) {
                                pKeyed = HtmlExtensionUtility.FindProp(o.ToString(), props);
                                dr[o.ToString()] = pKeyed.GetValue(en.Current, null);
                            }
                        } else {
                            foreach (PropertyInfo p in props) {
                                dr[p.Name] = p.GetValue(en.Current, null);
                            }

                        }
                        result.Rows.Add(dr);
                        indexer++;
                    }
                } else {
                    //TODO: Type this properly
                    throw new Exception("Can't bind to that data source. Consider using an IEnumerable data source such as an Array or collection");
                }
            }

            return result;

        }

        #endregion

    }
}
